using System;
using System.Collections.Generic;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Log
{
    /**
     * The FreeMarker logging facility. This is a polymorphic implementation
     * that will use whatever logging package it can find on the system:
     * Apache Jakarta Log4J, Apache Jakarta Avalon LogKit, JDK1.4 logging
     * (in this order). If it fails to find any of the above, logging will
     * be suppressed and a short notice output to System.err. You can use the
     * {@link #selectLoggerLibrary(int)} static method to force use of a specific
     * logger package, or to turn off logging.
     * @version $Id: Logger.java,v 1.24 2003/10/03 15:35:10 stephanmueller Exp $
     * @author Attila Szegedi
     */
    public abstract class Logger
    {
        /**
         * Constant used with {@link #selectLoggerLibrary(int)} that indicates the
         * engine should automatically lookup and use any available logger library.
         */
        public const int LibraryAuto = -1;

        /**
         * Constant used with {@link #selectLoggerLibrary(int)} that indicates the
         * engine should use no logger package (i.e. turn off logging).
         */
        public const int LibraryNone = 0;

        /**
         * Constant used with {@link #selectLoggerLibrary(int)} that indicates the
         * engine should use the <tt>java.util.logging</tt> logger package.
         */
        public const int LibraryJava = 1;

        /**
         * Constant used with {@link #selectLoggerLibrary(int)} that indicates the
         * engine should use the Apache Jakarta Avalon LogKit logger package.
         */
        public const int LibraryAvalon = 2;

        /**
         * Constant used with {@link #selectLoggerLibrary(int)} that indicates the
         * engine should use the Apache Jakarta Log4J logger package.
         */
        public const int LibraryLog4J = 3;

        private static readonly string[] LIBINIT =
        {
            "freemarker.log", "Null",
            "java.util.logging", "JDK14",
            "org.apache.log",    "Avalon",
            "org.apache.log4j",  "Log4J"
        };

        private static int _logLibrary;
        private static ILoggerFactory _factory;
        private static string _categoryPrefix = "";

        private static readonly Dictionary<string, Logger> Loggers = new Dictionary<string, Logger>();

        /**
         * Selects the logger library to use.
         * If you want to change the default setting, do it early in application
         * initialization phase, before calling any other FreeMarker API since once
         * various parts of the FreeMarker library bind to the logging subsystem,
         * the change in this value will have no effect on them.
         * @param library one of <tt>LIBRARY_XXX</tt> constants. By default,
         * {@link #LIBRARY_AUTO} is used.
         * @throws ClassNotFoundException if an explicit logging library is asked for
         * (that is, neither NONE, nor AUTO), and it is not found in the classpath.
         */
        public static void SelectLoggerLibrary(int library)
        {
            lock (typeof (Logger))
            {
                if (library < -1 || (library*2) >= LIBINIT.Length)
                {
                    throw new ArgumentException();
                }
                _logLibrary = library;
                _factory = CreateFactory();
            }
        }

        /**
         * Sets a category prefix. This prefix is prepended to any logger category
         * name. This makes it possible to have different FreeMarker logger categories
         * on a per-application basis (better said, per-classloader basis). By default
         * the category prefix is the empty string. If you set a non-empty category
         * prefix, be sure to include the trailing separator dot (i.e. "MyApp.")
         * If you want to change the default setting, do it early in application
         * initialization phase, before calling any other FreeMarker API since once
         * various parts of the FreeMarker library bind to the logging subsystem,
         * the change in this value will have no effect on them.
         */
        public static void SetCategoryPrefix(string prefix)
        {
            lock (typeof (Logger))
            {
                if(prefix == null)
                {
                    throw new ArgumentException();
                }
                _categoryPrefix = prefix;
            }
        }

        /**
         * Logs a debugging message.
         */
        public abstract void Debug(string message);

        /**
         * Logs a debugging message with accompanying throwable.
         */
        public abstract void Debug(string message, Exception e);

        /**
         * Logs an informational message.
         */
        public abstract void Info(string message);

        /**
         * Logs an informational message with accompanying throwable.
         */
        public abstract void Info(string message, Exception e);

        /**
         * Logs a warning message.
         */
        public abstract void Warn(string message);

        /**
         * Logs a warning message with accompanying throwable.
         */
        public abstract void Warn(string message, Exception e);

        /**
         * Logs an error message.
         */
        public abstract void Error(string message);

        /**
         * Logs an error message with accompanying throwable.
         */
        public abstract void Error(string message, Exception e);

        /**
         * Returns true if this logger will log debug messages.
         */
        public abstract bool IsDebugEnabled();

        /**
         * Returns true if this logger will log informational messages.
         */
        public abstract bool IsInfoEnabled();

        /**
         * Returns true if this logger will log warning messages.
         */
        public abstract bool IsWarnEnabled();

        /**
         * Returns true if this logger will log error messages.
         */
        public abstract bool IsErrorEnabled();

        /**
         * Returns true if this logger will log fatal error messages.
         */
        public abstract bool IsFatalEnabled();

        /**
         * Returns a logger for the specified category.
         * @param category a dot separated hierarchical category name. If a category
         * prefix is in effect, it is prepended to the category name.
         */
        public static Logger GetLogger(string category)
        {
            if (_factory == null)
            {
                lock (typeof (Logger))
                {
                    if (_factory == null)
                    {
                        //try
                        //{
                            SelectLoggerLibrary(LibraryAuto);
                        //}
                        // TODO: catch (ClassNotFoundException e)
                        //{
                        //    // This can't happen, really
                        //    throw new RuntimeException(e.Message);
                        //}
                    }
                }
            }

            category = _categoryPrefix + category;

            lock (Loggers)
            {
                Logger logger = Loggers.GetValueOrDefault(category);
                if (logger == null)
                {
                    logger = _factory.GetLogger(category);
                    Loggers.Add(category, logger);
                }
                return logger;
            }
        }

        private static ILoggerFactory CreateFactory()
        {
            if (_logLibrary == LibraryAuto)
            {
                for(int i = LIBINIT.Length / 2 - 1; i > 0; --i)
                {
                    //try
                    //{
                        return CreateFactory(i);
                    //}
                    // TODO: catch (ClassNotFoundException e)
                    //{
                    //    ;//Intentionally ignored
                    //}
                }
                Console.Error.WriteLine("*** WARNING: FreeMarker logging suppressed.");
                return new NullLoggerFactory();
            }
            else
            {
                return CreateFactory(_logLibrary);
            }
        }

        private static ILoggerFactory CreateFactory(int library)
        {
            string requiredPackage = LIBINIT[library * 2];
            string factoryType = LIBINIT[library * 2 + 1];

            ClassUtil.ForName(requiredPackage + ".Logger");
            return (ILoggerFactory) Activator.CreateInstance(
                Type.GetType("NFreeMarker.Log." + factoryType + "LoggerFactory"));
        }
    }
}